package com.gitee.qdbp.jdbc.stream;

import java.io.Serializable;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import com.gitee.qdbp.able.enums.LogicType;
import com.gitee.qdbp.able.exception.ServiceException;
import com.gitee.qdbp.able.jdbc.condition.DbUpdate;
import com.gitee.qdbp.able.jdbc.condition.DbUpdateForFieldCore;
import com.gitee.qdbp.able.jdbc.condition.DbUpdateForStartCore;
import com.gitee.qdbp.able.jdbc.condition.DbWhere;
import com.gitee.qdbp.able.jdbc.condition.DbWhereForFieldCore;
import com.gitee.qdbp.able.jdbc.condition.DbWhereForOnFieldless;
import com.gitee.qdbp.able.jdbc.condition.DbWhereForStartCore;
import com.gitee.qdbp.able.jdbc.condition.SkipType;
import com.gitee.qdbp.able.jdbc.fields.ExcludeFields;
import com.gitee.qdbp.able.jdbc.fields.Fields;
import com.gitee.qdbp.able.jdbc.fields.IncludeFields;
import com.gitee.qdbp.able.jdbc.ordering.Orderings;
import com.gitee.qdbp.able.jdbc.paging.Paging;
import com.gitee.qdbp.jdbc.api.CrudDao;
import com.gitee.qdbp.jdbc.exception.DbErrorCode;
import com.gitee.qdbp.jdbc.model.SimpleFieldColumn;
import com.gitee.qdbp.jdbc.utils.ParseTools;
import com.gitee.qdbp.tools.utils.VerifyTools;

/**
 * 单表查询流式操作
 *
 * @author zhaohuihua
 * @version 20210529
 */
public class CrudStream<T> implements Serializable {

    /** serialVersionUID **/
    private static final long serialVersionUID = 1L;

    protected final CrudDao<T> dao;

    public CrudStream(CrudDao<T> dao) {
        this.dao = dao;
    }

    /**
     * 查询指定字段<br>
     * <pre>
    List&lt;SysUser&gt; users = qdbcBoot.crudStream(SysUser.class)
        .select("id,userCode,realName") // 只查某些字段
        // .selectExclude("password") // 排除掉密码字段
        .list();
     * </pre>
     * 
     * @param fields 字段列表
     * @return 返回后续流式操作对象
     */
    public CrudOnAfterSelectStream<T> select(String... fields) {
        if (fields == null || fields.length == 0) {
            return new CrudOnAfterSelectStream<>(dao, Fields.ALL);
        } else {
            return new CrudOnAfterSelectStream<>(dao, new IncludeFields(fields));
        }
    }

    /**
     * 查询指定字段<br>
     * <pre>
    List&lt;SysUser&gt; users = qdbcBoot.crudStream(SysUser.class)
        .select("id,userCode,realName") // 只查某些字段
        // .selectExclude("password") // 排除掉密码字段
        .list();
     * </pre>
     * 
     * @param fields 字段列表
     * @return 返回后续流式操作对象
     */
    public CrudOnAfterSelectStream<T> select(List<String> fields) {
        return new CrudOnAfterSelectStream<>(dao, new IncludeFields(fields));
    }

    /**
     * 查询指定字段<br>
     * <pre>
    List&lt;SysUser&gt; users = qdbcBoot.crudStream(SysUser.class)
        .select(new IncludeFields("id,userCode,realName")) // 只查某些字段
        // .select(new IncludeFields("password")) // 排除掉密码字段
        .list();
     * </pre>
     * 
     * @param fields 字段列表
     * @return 返回后续流式操作对象
     */
    public CrudOnAfterSelectStream<T> select(Fields fields) {
        return new CrudOnAfterSelectStream<>(dao, fields);
    }

    /**
     * 排除指定字段, 只查询不在此列表中的字段<br>
     * <pre>
    List&lt;SysUser&gt; users = qdbcBoot.crudStream(SysUser.class)
        // .select("id,userCode,realName") // 只查某些字段
        .selectExclude("password") // 排除掉密码字段
        .list();
     * </pre>
     * 
     * @param fields 字段列表
     * @return 返回后续流式操作对象
     */
    public CrudOnAfterSelectStream<T> selectExclude(String... fields) {
        return new CrudOnAfterSelectStream<>(dao, new ExcludeFields(fields));
    }

    /**
     * 排除指定字段, 只查询不在此列表中的字段<br>
     * <pre>
    List&lt;SysUser&gt; users = qdbcBoot.crudStream(SysUser.class)
        // .select("id,userCode,realName") // 只查某些字段
        .selectExclude("password") // 排除掉密码字段
        .list();
     * </pre>
     * 
     * @param fields 字段列表
     * @return 返回后续流式操作对象
     */
    public CrudOnAfterSelectStream<T> selectExclude(List<String> fields) {
        return new CrudOnAfterSelectStream<>(dao, new ExcludeFields(fields));
    }

    /**
     * Where条件<br>
     * <pre>
    List&lt;SysUser&gt; users = qdbcBoot.crudStream(SysUser.class)
        .where("userType").equals(1)
            .and("createTime").between(DateTools.addDay(now, -1), DateTools.addDay(now, +1))
            .and().subCondition((w) -> {
                w.on("userCode").like("test")
                 .or("realName").like("test");
            })
            .end()
        .orderBy("createTime desc")
        .list();
     * 
     * @param fieldName 字段名称
     * @return 返回后续流式操作对象
     */
    public DbWhereForFieldCore<CrudOnAfterWhereByStream<T>> where(String fieldName) {
        DbWhere where = DbWhere.newEmptiableWhere();
        CrudOnAfterWhereByStream<T> next = new CrudOnAfterWhereByStream<>(dao, where);
        DbWhereForStartCore<CrudOnAfterWhereByStream<T>> start = new DbWhereForStartCore<>(next, where);
        return new DbWhereForFieldCore<>(start, SkipType.NONE, LogicType.AND, fieldName);
    }

    /**
     * 查询条件<br>
     * <pre>
    List&lt;SysUser&gt; users = qdbcBoot.crudStream(SysUser.class)
        .where().subCondition((w) -> {
                w.on("userCode").like("test")
                 .or("realName").like("test");
            })
            .and("userType").equals(1)
            .end()
        .orderBy("createTime desc")
        .list();
     * 
     * @return 返回后续流式操作对象
     */
    public DbWhereForOnFieldless<CrudOnAfterWhereByStream<T>> where() {
        DbWhere where = DbWhere.newEmptiableWhere();
        CrudOnAfterWhereByStream<T> next = new CrudOnAfterWhereByStream<>(dao, where);
        DbWhereForStartCore<CrudOnAfterWhereByStream<T>> start = new DbWhereForStartCore<>(next, where);
        return new DbWhereForOnFieldless<>(start, SkipType.NONE);
    }

    /**
     * 设置Where条件
     * 
     * @param where Where条件
     * @return 返回后续流式操作对象
     */
    public CrudOnAfterWhereByStream<T> whereBy(DbWhere where) {
        return new CrudOnAfterWhereByStream<>(dao, where);
    }

    /**
     * 从实体类构建Where对象<br>
     * <pre>
    List&lt;SysUser&gt; users = qdbcBoot.crudStream(SysUser.class)
        .whereBy(user)
        .list();
     * </pre>
     * 
     * @param entity 实体类
     * @return 返回后续流式操作对象
     * @see ParseTools#parseBeanToDbWhere(Object)
     */
    public CrudOnAfterWhereByStream<T> whereBy(T entity) {
        DbWhere where = dao.plugins().parseTools().parseBeanToDbWhere(entity);
        return new CrudOnAfterWhereByStream<>(dao, where);
    }

    /**
     * 从Map构建Where对象<br>
     * <pre>
    List&lt;SysUser&gt; users = qdbcBoot.crudStream(SysUser.class)
        .whereBy(map)
        .list();
     * </pre><br>
     * 只会包含clazz注解中通过@Column指定的字段名<br>
     * 应注意, 此时参数由前端传入, 条件不可控, 也有可能条件为空, 需要仔细检查条件内容, 防止越权操作 <pre>
     * 转换规则:
        fieldName$Equals(=), fieldName$NotEquals(!=), 
        fieldName$LessThen(<), fieldName$LessEqualsThen(<=), 
        fieldName$GreaterThen(>), fieldName$GreaterEqualsThen(>=), 
        fieldName$IsNull, fieldName$IsNotNull, 
        fieldName$Like, fieldName$NotLike, fieldName$Starts, fieldName$Ends, 
        fieldName$In, fieldName$NotIn, fieldName$Between
     * </pre>
     * 
     * @param map Map参数
     * @return 返回后续流式操作对象
     * @see ParseTools#parseBeanToDbWhere(Object)
     */
    public CrudOnAfterWhereByStream<T> whereBy(Map<String, Object> map) {
        DbWhere where = dao.plugins().parseTools().parseBeanToDbWhere(map);
        return new CrudOnAfterWhereByStream<>(dao, where);
    }

    /**
     * 以ID作为查询条件<br>
     * <pre>
    List&lt;SysUser&gt; users = qdbcBoot.crudStream(SysUser.class)
        .whereById("U0000001")
        .find();
        // logicalDelete();
        // physicalDelete();
     * </pre>
     * 
     * @param id 实体主键
     * @return 返回后续流式操作对象
     */
    public CrudOnAfterByIdStream<T> whereById(Serializable id) {
        VerifyTools.requireNotBlank(id, "id");
        return new CrudOnAfterByIdStream<>(dao, id);
    }

    /**
     * 以ID列表作为查询条件<br>
     * <pre>
    List&lt;SysUser&gt; users = qdbcBoot.crudStream(SysUser.class)
        .whereByIds("U0000001", "U0000002", ...)
        .orderBy("createTime desc")
        .list();
     * </pre>
     * 
     * @param ids ID列表
     * @return 返回后续流式操作对象
     */
    public CrudOnAfterWhereByStream<T> whereByIds(String... ids) {
        VerifyTools.requireNotBlank(ids, "ids");
        return doWhereByIds(Arrays.asList(ids));
    }

    /**
     * 以ID列表作为查询条件<br>
     * <pre>
    List&lt;SysUser&gt; users = qdbcBoot.crudStream(SysUser.class)
        .whereByIds("U0000001", "U0000002", ...)
        .orderBy("createTime desc")
        .list();
     * </pre>
     * 
     * @param ids ID列表
     * @return 返回后续流式操作对象
     */
    public CrudOnAfterWhereByStream<T> whereByIds(Collection<? extends Serializable> ids) {
        VerifyTools.requireNotBlank(ids, "ids");
        return doWhereByIds(ids);
    }

    protected CrudOnAfterWhereByStream<T> doWhereByIds(Collection<? extends Serializable> ids) {
        SimpleFieldColumn pk = dao.sqlHelper().fragment().getPrimaryKey();
        if (pk == null) { // 没有找到主键字段
            String details = "UnsupportedWhereByIds, class=" + dao.getBeanClass().getName();
            throw new ServiceException(DbErrorCode.DB_PRIMARY_KEY_FIELD_IS_UNRESOLVED, details);
        }
        String primaryField = pk.getFieldName();
        DbWhere where = DbWhere.newEmptiableWhere();
        where.on(primaryField).in(ids);
        return new CrudOnAfterWhereByStream<>(dao, where);
    }

    /**
     * 设置OrderBy条件<br>
     * <pre>
    List&lt;SysUser&gt; users = qdbcBoot.crudStream(SysUser.class)
        .orderBy("createTime desc")
        .list();
     * </pre>
     * 
     * @param orderings OrderBy条件
     * @return 返回后续流式操作对象
     */
    public CrudOnAfterOrderByStream<T> orderBy(String orderings) {
        return orderBy(Orderings.of(orderings));
    }

    /**
     * 设置OrderBy条件<br>
     * <pre>
    List&lt;SysUser&gt; users = qdbcBoot.crudStream(SysUser.class)
        .orderBy("createTime desc")
        .list();
     * </pre>
     * 
     * @param orderings OrderBy条件
     * @return 返回后续流式操作对象
     */
    public CrudOnAfterOrderByStream<T> orderBy(Orderings orderings) {
        return new CrudOnAfterOrderByStream<>(dao, DbWhere.NONE, orderings);
    }

    /**
     * 根据条件分页查询实体列表<br>
     * <pre>
    List&lt;SysUser&gt; users = qdbcBoot.crudStream(SysUser.class)
        .pageBy(1, 10) // 查第1页,每页10行
        .list().asPartList(); // 分页后返回的是PageList, 需要转换为普通List
        // .listFieldValues("deptCode", true, String.class).asPartList(); // 查询部门编号, true=去重
     * </pre>
     * 
     * @param pageIndex 第几页
     * @param pageSize 每页行数
     * @return 列表数据
     */
    public CrudOnAfterPagingStream<T> pageBy(int pageIndex, int pageSize) {
        return pageBy(new Paging(pageIndex, pageSize));
    }

    /**
     * 根据条件分页查询实体列表<br>
     * <pre>
    List&lt;SysUser&gt; users = qdbcBoot.crudStream(SysUser.class)
        .pageBy(1, 10, 88) // 查第1页,每页10行, 88=提前查询的总行数
        .list().asPartList(); // 分页后返回的是PageList, 需要转换为普通List
        // .listFieldValues("deptCode", true, String.class).asPartList(); // 查询部门编号, true=去重
     * </pre>
     * 
     * @param pageIndex 第几页
     * @param pageSize 每页行数
     * @param total 总行数(用于提前查询总行数的情况)
     * @return 列表数据
     */
    public CrudOnAfterPagingStream<T> pageBy(int pageIndex, int pageSize, int total) {
        return pageBy(new Paging(pageIndex, pageSize, total));
    }

    /**
     * 根据条件分页查询实体列表<br>
     * <pre>
    List&lt;SysUser&gt; users = qdbcBoot.crudStream(SysUser.class)
        .pageBy(1, 10, false) // 查第1页,每页10行, false=无需统计总数
        .list().asPartList(); // 分页后返回的是PageList, 需要转换为普通List
        // .listFieldValues("deptCode", true, String.class).asPartList(); // 查询部门编号, true=去重
     * </pre>
     * 
     * @param pageIndex 第几页
     * @param pageSize 每页行数
     * @param needCount 是否统计总数
     * @return 列表数据
     */
    public CrudOnAfterPagingStream<T> pageBy(int pageIndex, int pageSize, boolean needCount) {
        return pageBy(new Paging(pageIndex, pageSize, needCount));
    }

    /**
     * 根据条件分页查询实体列表<br>
     * <pre>
    List&lt;SysUser&gt; users = qdbcBoot.crudStream(SysUser.class)
        .pageBy(new Paging(1,10,false)) // 查第1页,每页10行, false=无需统计总数
        .list().asPartList(); // 分页后返回的是PageList, 需要转换为普通List
        // .listFieldValues("deptCode", true, String.class).asPartList(); // 查询部门编号, true=去重
     * </pre>
     * 
     * @param paging 分页参数
     * @return 列表数据
     */
    public CrudOnAfterPagingStream<T> pageBy(Paging paging) {
        return new CrudOnAfterPagingStream<>(dao, DbWhere.NONE, Orderings.NONE, paging);
    }

    /**
     * 根据条件查询实体列表<br>
     * <pre>
    List&lt;SysUser&gt; users = qdbcBoot.crudStream(SysUser.class)
        .list();
     * </pre>
     * 
     * @return 列表数据
     */
    public List<T> list() {
        return dao.list(Fields.ALL, DbWhere.NONE, Orderings.NONE);
    }

    /**
     * 根据条件查询某个字段的值列表<br>
     * <pre>
    List&lt;String&gt; deptCodes = qdbcBoot.crudStream(SysUser.class)
        .listFieldValues("deptCode", true, String.class); // 查询部门编号, true=去重
     * </pre>
     * 
     * @param fieldName 指定字段名
     * @param distinct 是否去重
     * @param valueClazz 字段值类型
     * @return 字段的值列表
     */
    public <V> List<V> listFieldValues(String fieldName, boolean distinct, Class<V> valueClazz)
            throws ServiceException {
        return dao.listFieldValues(fieldName, distinct, DbWhere.NONE, Orderings.NONE, valueClazz);
    }

    /**
     * 根据条件统计实体数量<br>
     * <pre>
    int total = qdbcBoot.crudStream(SysUser.class)
        .count();
     * </pre>
     * 
     * @return 数据数量
     */
    public int count() throws ServiceException {
        return dao.count(DbWhere.NONE);
    }

    /**
     * 根据条件分组统计实体数量<br>
     * <pre>
    Map&lt;String, Integer&gt; result = qdbcBoot.crudStream(SysUser.class)
        .groupCount("userType");
    
    SELECT USER_TYPE, COUNT(*) FROM SYS_USER
        GROUP BY USER_TYPE
     * </pre>
     * 
     * @param groupBy 分组条件
     * @return 分组统计结果
     */
    public Map<String, Integer> groupCount(String groupBy) throws ServiceException {
        return dao.groupCount(groupBy, DbWhere.NONE);
    }

    /**
     * 开始递归查询<br>
     * recursiveBy(codeField, parentField) 按什么字段进行递归查询<br>
     * startBy 起始编号<br>
     * filterBy 数据过滤条件, 过滤哪些数据参与递归 (如数据状态,租户编号等条件)<br>
     * searchBy 结果搜索条件 (如用户输入的查询条件) 这些条件如果放在filterWhere中将无法生成完整的树<br>
     * 注意: 查询结果包括startCode节点的自身记录, 如果不需要应在searchWhere条件排除<br>
     * <pre>
    List&lt;SysDept&gt; depts = qdbcBoot.crudStream(SysDept.class)
        .recursiveBy("deptCode", "parentCode") // 按什么进行递归
        .startBy("D01", "D02") // 起始编号
        .filterBy("tenantCode").equals("T001").end() // 租户编号
        .searchBy("id").notIn("D01", "D02").end() // 查询子节点, 但不要包含起始编号自身
        .listChildren();
        // .listParents();
     * </pre>
     * 
     * @param codeField 编号字段名
     * @param parentField 上级编号字段名
     * @return 返回后续流式操作对象
     */
    public RecursiveQueryStream<T> recursiveBy(String codeField, String parentField) {
        return new RecursiveQueryStream<>(dao, codeField, parentField);
    }

    /**
     * 实体类操作<br>
     * <pre>
    int rows = qdbcBoot.crudStream(SysUser.class)
        .entity(user)
        .insert();
        // .update();
     * </pre>
     * 
     * @param entity 实体类
     * @return 返回后续流式操作对象
     */
    public CrudOnAfterEntityStream<T> entity(T entity) {
        VerifyTools.requireNonNull(entity, "entity");
        return new CrudOnAfterEntityStream<>(dao, entity);
    }

    /**
     * 实体类操作<br>
     * <pre>
    int rows = qdbcBoot.crudStream(SysUser.class)
        .entity(user)
        .insert();
        // .update();
     * </pre>
     * 
     * @param entity 实体类
     * @return 返回后续流式操作对象
     */
    public CrudOnAfterEntityStream<T> entity(Map<String, Object> entity) {
        VerifyTools.requireNotBlank(entity, "entity");
        return new CrudOnAfterEntityStream<>(dao, entity);
    }

    /**
     * 实体批量操作<br>
     * <pre>
    List&lt;String&gt; ids = qdbcBoot.crudStream(SysUser.class)
        .entities(user)
        .insert();
        // .update();
     * </pre>
     * 
     * @param entities 实体类
     * @return 返回后续流式操作对象
     */
    public CrudOnAfterEntitiesStream<T> entities(List<?> entities) {
        return new CrudOnAfterEntitiesStream<>(dao, entities);
    }

    /**
     * 设置更新条件
     * 
     * @param ud 更新条件
     * @return 返回后续流式操作对象
     */
    public CrudOnAfterSetByStream<T> updateBy(DbUpdate ud) {
        return new CrudOnAfterSetByStream<>(dao, ud);
    }

    /**
     * 更新操作<br>
     * <pre>
    int rows = qdbcBoot.crudStream(SysUser.class)
        .updateSet("userName").to("zhaohuihua") // 用户名修改为指定值
            .set("memberScore").add(+100) // 会员积分增加100
            .set("memberScore").add(-100) // 会员积分减少100
            .set("userState").toNull() // 用户状态修改为空
            .end()
        // .whereById("U0000001")
        .where("userId").equals("U0000001")
            .and("userState").in(UserState.NORMAL, UserState.LOCKED)
            .end()
        .update();
     * </pre>
     * 
     * @param fieldName 字段名
     * @return 返回后续流式操作对象
     */
    public DbUpdateForFieldCore<CrudOnAfterSetByStream<T>> updateSet(String fieldName) {
        DbUpdate ud = new DbUpdate();
        CrudOnAfterSetByStream<T> next = new CrudOnAfterSetByStream<>(dao, ud);
        DbUpdateForStartCore<CrudOnAfterSetByStream<T>> start = new DbUpdateForStartCore<>(next, ud);
        return new DbUpdateForFieldCore<>(start, fieldName);
    }

    /**
     * 更新操作<br>
     * <pre>
    int rows = qdbcBoot.crudStream(SysUser.class)
        .updateBy()
            // 用户名修改为指定值
            // .ifHasValue() 如果userName有值(不为空)才会添加该条件
            .ifHasValue().set("userName").to(userName)
            // 用户状态修改为空
            // ifTrue(resetState) 如果resetState=true才会添加该条件
            .ifTrue(resetState).set("userState").toNull()
            .end()
        // .whereById("U0000001")
        .where("userId").equals("U0000001")
            .and("userState").in(UserState.NORMAL, UserState.LOCKED)
            .end()
        .update();
     * </pre>
     * 
     * @return 返回后续流式操作对象
     */
    public DbUpdateForStartCore<CrudOnAfterSetByStream<T>> updateBy() {
        DbUpdate ud = new DbUpdate();
        CrudOnAfterSetByStream<T> next = new CrudOnAfterSetByStream<>(dao, ud);
        return new DbUpdateForStartCore<>(next, ud);
    }
}
